#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "mpi.h"

#include "simpleFFT.h"
#include "Complex.h"
#include "FFTEngine.h"
#include "CommLayer.h"
#include "BlockedRadix.h"
#include "TransRadix.h"
#include "AllToAllBench.h"
#include "BinExBench.h"


//define N 262144
//define N 32*262144
#define N 8
#define P 8


double runMany(FFTEngine *fftE, CommLayer *comm, long numPoints,int numRuns, bool commOnly) {
	
	double startTime = 0.0;
	double endTime = 0.0;
	
	//create the fft engine
	//BlockedRadix *fftE = new BlockedRadix(numPoints,comm);

	//create the initial array
	Complex *arr = (Complex *) malloc(sizeof(Complex)*numPoints);
	//Complex *arr = new Complex[numPoints];

	if (!arr) {
		long blocks = sizeof(Complex) * (long)numPoints;
		printf("Malloc Failed! for %ld\n",blocks);
	}

	for (int i=0; i < numPoints; i++) { 
		arr[i] = Complex((i+1)*1.0,0.0); 
	}		
		


	//distribute the initial data
	if (comm->my_rank == 0) {
		fftE->distributeInitData(arr);
	} else {
		fftE->aquireInitData();
	}


	
	MPI_Barrier(MPI_COMM_WORLD);
	
	startTime = MPI_Wtime();


	
	for (int i=0; i < numRuns; i++) {
		fftE->executeFFT(commOnly);
	}	

	MPI_Barrier(MPI_COMM_WORLD);


	endTime = MPI_Wtime();


	//delete[] arr;
	free(arr);	
	//delete fftE;	


	return (endTime - startTime)/numRuns;
}

void testSimple(CommLayer *comm) {
	
	#define NN 64
	Complex vals[NN]; //FFT values	
	TransRadix *fftE = new TransRadix(NN,comm);


	comm->sayHello(); //verify all nodes
	//fftE->unitTests(); //run unit tests

	//create starting values
	for (int i=0; i < NN; i++) { vals[i] = Complex((i+1)*1.0,0.0); }		

	fftE->runFFT(vals); //distribute start values, compute fft, and return result to proc=0

	//print the results
	//if (comm->my_rank == 0) {
	//	printf("\n\nResults:\n");
	//	Complex::printArray(vals,NN);
	//}
	
	delete fftE;
}




int main(int argc, char * argv[])
{

	double time;

	CommLayer *comm = new CommLayer(argc,argv);
	int p = comm->p;
	int n = p*p;
	

	//warmup
	//comm->sayHello(); //verify all nodes
	//testSimple(comm);


	MPI_Barrier(MPI_COMM_WORLD);

	//All-To-All benchmarks
	AllToAllBench *benchFFT = new AllToAllBench(n,comm);
	benchFFT->executeFFT(false);
	benchFFT->executeFFT(false);
	benchFFT->executeFFT(false);


	//BenEx benchmarks
	BinExBench *binexFFT = new BinExBench(n,comm);
	binexFFT->executeFFT(false);
	binexFFT->executeFFT(false);
	binexFFT->executeFFT(false);




	//Time Transpose version
	TransRadix *transFFT = new TransRadix(n,comm);

	time = runMany(transFFT,comm,n,100,false);
	if (comm->my_rank == 0) {
		printf("Transpose_full N=%d time=%lf\n",n,time);
	}

	time = runMany(transFFT,comm,n,100,true);
	if (comm->my_rank == 0) {
		printf("Transpose_comm N=%d time=%lf\n",n,time);
	}


	delete transFFT;
	




	//Time blocked Version
	BlockedRadix *blockedFFT = new BlockedRadix(n,comm);

	time = runMany(blockedFFT,comm,n,100,false);
	if (comm->my_rank == 0) {
		printf("Blocked_full N=%d time=%lf\n",n,time);
	}

	time = runMany(blockedFFT,comm,n,100,true);
	if (comm->my_rank == 0) {
		printf("Blocked_comm N=%d time=%lf\n",n,time);
	}

	delete blockedFFT;




	delete comm;

	return EXIT_SUCCESS;
}
